#include "peripheral.h"
#include "sync-queue.hpp"

#include <chrono>
#include <vector>

void FCFSPeripheral::run(std::shared_ptr<SyncQueue<Interupt>> interrupt_channel)
{
    SyncQueue<IORequest> &request_channel = io_queue;
    const std::string &name_ref = get_name();
    int id = get_id();

    thread_handle = std::thread([&name_ref, id, &request_channel, interrupt_channel]()
                                {
        while(1)
        {
            auto req = request_channel.pop();
            switch(req->type)
            {
                case IORequest::Type::SHUTDOWN:
                    return ;
                    break;

                case IORequest::Type::REQUEST:
                    for(int i = 0; i < req->arg1 ; i++)
                    {
                        std::this_thread::sleep_for(std::chrono::duration<int,std::milli>(1000));
                    }
                    interrupt_channel->push(Interupt{0,id,req->arg3,req->arg4,Interupt::IO_READY});
            }
        } });
}

void SSTFPeripheral::run(std::shared_ptr<SyncQueue<Interupt>> interrupt_channel)
{
    SyncQueue<IORequest> &request_channel = io_queue;
    const std::string &name_ref = get_name();
    int id = get_id();
    int seek = seek_start;

    thread_handle = std::thread([&name_ref, id, &request_channel, interrupt_channel, seek]()
                                {
        int peripheral_seek = seek;
        std::vector<std::shared_ptr<IORequest>> request_v;

        while(1)
        {
            std::shared_ptr<IORequest> tmp;
            while (tmp = request_channel.try_pop())
            {
                if(tmp->type==IORequest::SHUTDOWN) return;
                request_v.push_back(tmp);
            }

            long long dis_min=INT64_MAX;
            int idx = -1;

            for(int i=0;i<request_v.size();i++)
            {
                int dis = request_v[i]->arg4 - peripheral_seek;
                if(dis < 0) dis = -dis;
                if(dis < dis_min)
                {
                    dis_min=dis;
                    idx = i;
                }
            }

            if(idx == -1) continue;

            for(int i = 0; i < request_v[idx]->arg1 ; i++)
            {
                std::this_thread::sleep_for(std::chrono::duration<int,std::milli>(1000));
            }
            interrupt_channel->push(Interupt{0,id,request_v[idx]->arg3,request_v[idx]->arg4,Interupt::IO_READY});
            peripheral_seek = request_v[idx]->arg4;

            request_v.erase(request_v.begin()+idx);
        } });
}

#ifdef UNIT_TEST

#include <gtest/gtest.h>

TEST(PERIPHERAL_TEST, FCFS_TEST)
{
    std::shared_ptr<SyncQueue<Interupt>> callback = std::make_shared<SyncQueue<Interupt>>();
    Peripheral *p = new FCFSPeripheral;
    p->set_id(1);
    p->set_name("FCFS");

    for (int i = 0; i < 10; i++)
    {
        p->io_queue.push(IORequest{0, 0, 0, i, IORequest::REQUEST});
    }

    p->run(callback);

    p->io_queue.push(IORequest{0, 0, 0, 0, IORequest::SHUTDOWN});
    p->thread_handle.join();

    for (int i = 0; i < 10; i++)
    {
        auto int_ptr = callback->pop();
        EXPECT_EQ(int_ptr->arg4, i);
    }
}

TEST(PERIPHERAL_TEST, SSTF_TEST)
{
    std::shared_ptr<SyncQueue<Interupt>> callback = std::make_shared<SyncQueue<Interupt>>();
    Peripheral *p = new SSTFPeripheral;
    p->set_id(1);
    p->set_name("FCFS");

    p->io_queue.push(IORequest{0, 0, 0, 5, IORequest::REQUEST});
    p->io_queue.push(IORequest{0, 0, 0, 21, IORequest::REQUEST});
    p->io_queue.push(IORequest{0, 0, 0, -10, IORequest::REQUEST});

    p->run(callback);

    auto int_ptr = callback->pop();
    EXPECT_EQ(int_ptr->arg4, 5);

    int_ptr = callback->pop();
    EXPECT_EQ(int_ptr->arg4, -10);

    int_ptr = callback->pop();
    EXPECT_EQ(int_ptr->arg4, 21);

    p->io_queue.push(IORequest{0, 0, 0, 0, IORequest::SHUTDOWN});
    p->thread_handle.join();
}

TEST(PERIPHERAL_TEST, SSTF_TEST_SEEK)
{
    SSTFPeripheral *p = new SSTFPeripheral;
    p->seek_start = 67;
    auto callback = std::make_shared<SyncQueue<Interupt>>();
    int tracks[] = {98, 25, 63, 97, 56, 51, 55, 55, 6};
    int seq[] = {63, 56, 55, 55, 51, 25, 6, 97, 98};

    for (int i = 0; i < 9; i++)
    {
        p->io_queue.push(IORequest{0, 0, 0, tracks[i], IORequest::Type::REQUEST});
    }

    p->run(callback);

    for (int i = 0; i < 9; i++)
    {
        auto tmp = callback->pop();
        EXPECT_EQ(tmp->arg4, seq[i]);
    }

    p->io_queue.push(IORequest{0, 0, 0, 0, IORequest::Type::SHUTDOWN});
    p->thread_handle.join();
}

#endif